-
-
Notifications
You must be signed in to change notification settings - Fork 15
feat: framework refactor + decouple from Hyperf #349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
binaryfire
wants to merge
588
commits into
hypervel:0.4
Choose a base branch
from
binaryfire:feature/hyperf-decouple
base: 0.4
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Add static $resolvedBuilderClasses property to Model for caching - Update newEloquentBuilder to use cache (Hypervel optimization) - Rename newModelBuilder to newEloquentBuilder in test (Laravel naming)
…eritance) Laravel does NOT inherit ScopedBy attributes from parent classes. PHP attributes are not inherited by default, and Laravel does not implement custom inheritance logic. Updated tests to verify this behavior is preserved (the old Hypervel added inheritance which would be a Laravel API deviation).
In Hyperf containers, get() retrieves a bound instance while make() creates a new one. Using make() bypassed mocked Application instances in tests. This matches the old Hypervel behavior.
In Hypervel, make() always creates a fresh instance bypassing bindings, while get() returns the bound singleton. Changed three locations: - Factory::withFaker() - Faker Generator should be bound singleton - ModelInspector::getObservers() - ModelListener holds stateful observer registrations - ConnectionFactory::createConnector() - Honor user's explicit custom connector binding
Avoid unnecessary container access in bootHasEvents() when no ObservedBy attributes exist. This matches the pre-port behavior and allows models to be instantiated without a container when they don't use observers.
…Test - EventFake now implements Hypervel\Event\Contracts\Dispatcher instead of just Psr\EventDispatcherInterface, allowing it to be used with Model events - Added missing interface methods: getListeners, hasWildcardListeners, getRawListeners - Fixed method signatures to match interface (listen with priority, push with mixed) - BroadcasterTest: Removed old Hyperf Booted::$container usage - BroadcasterTest: Updated model stubs to properly override resolveRouteBinding - BroadcasterTest: Assertions now verify Model instances with correct bound values
…nction - Port helpers.php from Laravel with Hypervel namespaces - Keep Swoole-specific environment() function - Fix env() to call \Hypervel\Support\env() (namespaced function) - Remove non-existent Env class reference
- Replace use Hyperf\Contract\Arrayable with Hypervel\Contracts\Support\Arrayable - Replace use Hyperf\Contract\Jsonable with Hypervel\Contracts\Support\Jsonable - Update PHPDoc FQCN references in Facades
- Added validateModelClass() to ensure registered model classes exist and extend Model (matches pre-port Hypervel behavior) - Updated ModelListenerTest for new API (ContainerInterface + Dispatcher) - Added tests for invalid model class and non-Model class registration - Deleted ObserverManagerTest (ObserverManager class no longer exists, functionality moved to ModelListener.registerObserver)
- Created shared base class with migrateRefresh=true and custom migration path - All Tmp integration tests now extend TmpIntegrationTestCase - Fixes test pollution when running after Database tests (migrations weren't running because RefreshDatabaseState::$migrated was already true) - Removed redundant getDatabaseDriver() and migrateFreshUsing() from child classes
- Updated TransactionManager class reference to DatabaseTransactionsManager - Fixed testHaltingEventExecution to expect Laravel behavior (halt on first non-null response, not after any listener)
Create Foundation test stubs and migrations to avoid Workbench dependency: - tests/Foundation/Stubs/User.php with model and factory - tests/Foundation/migrations/ with users table migration
Use Hypervel\Database\ConnectionInterface instead of Hyperf\Contract\ConnectionInterface
- Add toJson() method to ApiResource (implements Jsonable interface) - Cast int keys to string in Arr::set() for PHP 8 strict types - Allow callable in Collection::where() signature - Port Fluent from Laravel with modernized types - Add Arrayable handling to PendingRequest::normalizeRequestOptions() - Add class_map files to composer.json autoload for test compatibility
- Add newEloquentBuilder() override in HasNode trait to return custom QueryBuilder - Fix Grammar::compileFrom() to accept Expression|string - Fix Grammar::parameter() return type to string|int|float - Fix test ordering by 'name' instead of non-existent 'title' column - Add Postgres sequence reset after seeding with explicit IDs
The Hyperf package was binding db.connector.sqlite to its own connector, overriding our ported SQLiteConnector.
- Add missing tearDown() with m::close() to prevent mock leakage - Add missing setConnectionName() for afterCommit job tests - Fix mock return values: insertGetId returns int, insert returns bool - Fix TransactionManager -> DatabaseTransactionsManager class name
The Migrator::setConnection() can receive null and passes it to setSource(), so the parameter must be nullable.
- Replace Str::from/fromAll with enum_value() function - Change BackedEnum to UnitEnum for broader enum support - Remove hardcoded 'testing' database connection from tests - Add self-contained User model with factory for FrontendRequestsAreStatefulTest - Add sanctum_test_users migration
Matches Laravel behavior - Arr::get() handles null gracefully by returning the default value via the accessible() check. The previous strict ArrayAccess|array type broke code passing null (e.g., LinkedInProvider passing $avatar from Arr::first() which can be null).
The Tmp directory contains temporary integration tests created during development that require specific database setup and don't exist on main.
…on-rules feat: port missing Laravel validation rules
- Fix helpers.php: add missing return null in optional(), use while loop in retry() - Fix CacheCommandMutex: forceRelease() returns void, return true after - Fix ProcessInspector: remove incorrect @var annotation - Fix Onceable: add missing return null in tryFromTrace() - Fix ModelWatcher: use ModelEvent instead of non-existent Event class - Fix Response/CoreMiddleware/Logger: use toJson()/toArray() for Hypervel contracts - Update ResponseTest to use Hypervel contracts with proper toJson() implementation
- Worker: Fix broken tap pattern, use until() like Laravel, update EventDispatcher type - FailedJobProviders: Replace dynamic whereConnection/whereQueue with explicit where() - HasFactory: Add phpstan ignore for optional $factory property check - AutoScaler: Add type annotation to fix sum() type inference - Horizon RedisQueue: Add type assertion for RedisJob in pop() callback - Telescope migration: Fix getConnection return type to ?string - Tests: Update event namespaces and Connection mock for database port
find() has explicit return type object|array|null in its method signature, unlike first() which uses the generic TValue (stdClass) from BuildsQueries.
- Split static analysis into parallel jobs for src and types - Use single PHP 8.4 version (type checking doesn't vary by PHP) - Add composer caching (shares cache with other workflows) - Update to actions/checkout@v6 - Remove @phpstan-ignore for PHP 8.4 lazy object methods (now recognized)
- Install gmp extension in tests workflow for GMP casting tests - Add missing RequiresPhpExtension import so tests skip gracefully when gmp is unavailable (e.g., local development)
The reconnect test triggers PooledConnection's reconnector which logs "Database connection refreshing." - this is expected behavior, not an error.
- Change Relation::get(), BelongsToMany::get(), HasOneOrManyThrough::get() return types from EloquentCollection to BaseCollection (Support\Collection) - Change applyAfterQueryCallbacks() signature from mixed->mixed to Collection->Collection - Move workbench migrations/factories/models to _tmp (tests should create own schema) - Add "When Tests Expose Source Code Type Errors" section to porting guide - Add Laravel integration tests (in progress, temporarily in _tmp) The afterQuery callback mechanism allows replacing query results with a different Collection subtype. Since Eloquent\Collection extends Support\Collection, using the base type correctly covers both normal returns and afterQuery replacements.
- Port WithMigration attribute with type alias support ('cache', 'queue',
'session' all map to 'laravel')
- Add testbench helper functions (after_resolving, load_migration_paths,
default_migration_path, join_paths)
- Override Testbench TestCase::setUpTraits() to wrap database operations
in setUpDatabaseRequirements(), ensuring WithMigration attributes are
processed before migrations run
- Add workbench test migrations (users, cache, jobs, notifications tables)
- Add workbench cache config with database store
- Update AttributesTest for new WithMigration behavior
- Port DatabaseCacheStoreTest as first test using WithMigration
Move Laravel's database integration tests from _tmp staging area to tests/Integration/Database/Laravel/ as the starting point for the porting effort outlined in PLAN.md. These tests are unmodified Laravel tests that will be ported one by one to work with Hypervel's stricter typing and coroutine architecture.
DatabaseCacheStoreTest and DatabaseLockTest require cache package methods that haven't been ported yet. Move to Todo/ with markTestSkipped until the cache package is fully ported. Missing methods: - DatabaseStore: forgetIfExpired(), getConnection() - Lock: isOwnedBy(), isOwnedByCurrentProcess() (public) - DatabaseLock: getConnectionName()
Hypervel does not support the ::read/::write connection suffix syntax that Laravel uses to force all queries to a specific PDO. This feature is incompatible with Swoole's connection pooling architecture. Changes: - Remove parseConnectionName() from DatabaseManager - Remove setPdoForType() from DatabaseManager - Simplify configure() - no longer accepts $type parameter - Remove $readWriteType property and setReadWriteType() from Connection - Remove getNameWithReadWriteType() - replaced with getName() - Simplify latestReadWriteTypeUsed() - returns $latestPdoTypeRetrieved Normal read/write splitting still works: - Configure read/write arrays in database config - Automatic routing: reads go to readPdo, writes go to pdo - Sticky sessions work (reads follow writes to primary) - Transaction isolation works (all queries use primary) Tests updated: - DatabaseConnectionsTest: REMOVED comments for unsupported features - DatabaseQueryExceptionTest: Changed mysql::read to mysql_replica - Porting guide updated with removed test documentation pattern
The testing DatabaseConnectionResolver caches connections statically to prevent pool exhaustion (since tests don't use defer() to release connections). This caused test pollution where query logs, event listeners, and other connection state leaked between tests. Added resetCachedConnections() method that calls resetForPool() on all cached connections, invoked at the start of each test in TestCase::setUp(). Also: - Port DatabaseConnectionsTest for Hypervel (removed dynamic connection tests incompatible with Swoole pooling) - Exclude unported Laravel integration tests from default test suite - Fix BootTraitsTest missing method that AttributeParser reflects on
- Remove redundant empty() check in HandlesDatabases filter callback - Add @method annotations to Testbench TestCase for trait-provided methods (refreshDatabase, runDatabaseMigrations, beginDatabaseTransaction, etc.) - Apply CS-fixer formatting to unported Laravel integration tests
- Add verifyConfiguration() method to BcryptHasher and HashManager to support the 'hashed' cast type used by DatabaseCustomCastsTest - Add Model::clearBootedModels() to DatabaseMigrations trait to prevent test pollution when Event::fake() creates a new EventFake (models must re-boot to register event callbacks with the current dispatcher) - Port DatabaseCustomCastsTest with Hypervel namespaces and strict types - Port DatabaseEloquentBroadcastingTest with Hypervel namespaces and updated channel name assertions to match Hypervel namespace
…delCustomCastingTest - Update namespaces from Illuminate to Hypervel - Add strict return types to afterRefreshingDatabase methods - Update model property types (protected array $guarded, etc.) - Update caster classes to match Hypervel's stricter interface signatures - Fix Decimal test fixture to handle float/int values from arithmetic operations
…ToManyTest - Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures - Fix sync() method to accept null (was missing from union type) - sync(), syncWithoutDetaching(), syncWithPivotValues() now accept null - Internal parseIds() already handled null correctly via (array) cast - Update expected exception messages to use Hypervel namespace
…ectionLoadCountTest, EloquentCollectionLoadMissingTest and Fixtures - Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures - Port Fixtures/User, Post, PostStringyKey, NamedScopeUser
- Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures
- Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures
- Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures
- Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures - Adapt testItOnlyEagerLoadsRequiredModels for Hypervel's event dispatcher (wildcard listeners receive spread payload, not array) - Comment out original test for restoration when illuminate/events is ported
- Update namespaces from Illuminate to Hypervel - Add strict types to model properties and method signatures - Remove event dispatcher mocking from MassPrunableTest (incompatible with Hypervel's event() helper) - functional assertions preserved - Add TODO comments for event verification when illuminate/events is ported
- Add CallQueuedListener, Dispatcher classes - Add QueueRoutes class to queue package - Add ResolvesQueueRoutes trait to support package
…in main composer.json
…andleEventsAfterCommit)
- Update CallQueuedListener data PHPDoc to allow string|array - Use bind() instead of singleton() in EventServiceProvider - Add queue() method to Broadcasting Factory contract - Add phpstan ignore for false positive in defer()
- Add listenersCache for prepared listeners (avoids creating new closures on every dispatch in long-running processes) - Use Context for deferred events state (coroutine-safe, prevents race conditions when multiple coroutines defer events simultaneously) - Invalidate listenersCache in listen(), setupWildcardListen(), forget()
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Hi @albertcht. This isn't ready yet but I'm opening it as a draft so we can begin discussions and code reviews. The goal of this PR is to refactor Hypervel to be a fully standalone framework that is as close to 1:1 parity with Laravel as possible.
Why one large PR
Sorry about the size of this PR. I tried spreading things across multiple branches but it made my work a lot more difficult. This is effectively a framework refactor - the database package is tightly coupled to many other packages (collections, pagination, pool) as well as several support classes, so all these things need to be updated together. Splitting it across branches would mean each branch needs multiple temporary workarounds + would have failing tests until merged together, making review and CI impractical.
A single large, reviewable PR is less risky than a stack of dependent branches that can't pass CI independently.
Reasons for the refactor
1. Outdated Hyperf packages
It's been difficult to migrate existing Laravel projects to Hypervel because Hyperf's database packages are quite outdated. There are almost 100 missing methods, missing traits, it doesn't support nested transactions, there are old Laravel bugs which haven't been fixed (eg. JSON indices aren't handled correctly), coroutine safety issues (eg. model
unguard(),withoutTouching()). Other packages like pagination, collections and support are outdated too.Stringablewas missing a bunch of methods and traits, for example. There are just too many to PR to Hyperf at this point.2. Faster framework development
We need to be able to move quickly and waiting for Hyperf maintainers to merge things adds a lot of friction to framework development. Decoupling means we don't need to work around things like PHP 8.4 compatibility while waiting for it to be added upstream. Hyperf's testing package uses PHPUnit 10 so we can't update to PHPUnit 13 (and Pest 4 in the skeleton) when it releases in a couple of weeks. v13 has the fix that allows
RunTestsInCoroutineto work with newer PHPUnit versions. There are lots of examples like this.3. Parity with Laravel
We need to avoid the same drift from Laravel that's happened with Hyperf since 2019. If we're not proactive with regularly merging Laravel updates every week we'll end up in the same situation. Having a 1:1 directory and code structure to Laravel whenever possible will make this much easier. Especially when using AI tools.
Most importantly, we need to make it easier for Laravel developers to use and contribute to the framework. That means following the same APIs and directory structures and only modifying code when there's a good reason to (coroutine safety, performance, type modernisation etc).
Right now the Hypervel codebase is confusing for both Laravel developers and AI tools:
hypervel/contractspackage, the Hyperf database code is split across 3 packages, the Hyperf pagination package ishyperf/paginatorand nothyperf/pagination)static::registerCallback('creating')vsstatic::creating())ConfigProviderand LaravelServiceProviderpatterns across different packages is confusing for anyone who doesn't know HyperfThis makes it difficult for Laravel developers to port over apps and to contribute to the framework.
4. AI
The above issues mean that AI needs a lot of guidance to understand the Hypervel codebase and generate Hypervel boilerplate. A few examples:
hypervel/contractsfor contracts) and then have to spend a lot of time grepping for things to find them.And so on... This greatly limits the effectiveness of building Hypervel apps with AI. Unfortunately MCP docs servers and CLAUDE.md rules don't solve all these problems - LLMs aren't great at following instructions well and the sheer volume of Laravel data they've trained on means they always default to Laravel-style code. The only solution is 1:1 parity. Small improvements such as adding native type hints are fine - models can solve that kind of thing quickly from exception messages.
What changed so far
New packages
illuminate/databaseportilluminate/collectionsportilluminate/paginationportilluminate/contracts)hyperf/pool)Macroableto a separate package for Laravel parityRemoved Hyperf dependencies so far
Database package
The big task was porting the database package, making it coroutine safe, implementing performance improvements like static caching and modernising the types.
whereLike,whereNot,groupLimit,rawValue,soleValue, JSON operations, etc.Collections package
Contracts package
Support package
hyperf/tappable,hyperf/stringable,hyperf/macroable,hyperf/codecdependenciesStr,Envand helper classes from LaravelHypervel\Contextwrappers (will be portinghyperf/contextsoon)Number::useCurrency()wasn't actually setting the currency)Coroutine safety
withoutEvents(),withoutBroadcasting(),withoutTouching()now use Context instead of static propertiesUnsetContextInTaskWorkerListenerto clear database context in task workersConnection::resetForPool()to prevent state leaks between coroutinesDatabaseTransactionsManagercoroutine-safeBenefits
Testing status so far
What's left (WIP)
The refactor process
Hyperf's Swoole packages like
pool,coroutine,contextandhttp-serverhaven't changed in many years so porting these is straightforward. A lot of the code can be simplified since we don't need SWOW support. And we can still support the ecosystem by contributing any improvements we make back to Hyperf in separate PRs.Eventually I'll refactor the bigger pieces like the container (contextual binding would be nice!) and the config system (completely drop
ConfigProviderand move entirely to service providers). But those will be future PRs. For now the main refactors are the database layer, collections and support classes + the simple Hyperf packages. I'll just port the container and config packages as-is for now.Let me know if you have any feedback, questions or suggestions. I'm happy to make any changes you want. I suggest we just work through this gradually, as an ongoing task over the next month or so. I'll continue working in this branch and ping you each time I add something new.